<?php

class RedisModel {

    /**
     * 表名
     * @var string 
     */
    static public $table;

    /**
     * 主键ID字段名
     * @var int 
     */
    protected $_id = 'id';

    /**
     * 可填充字段数组和对应的数据类型
     * 
     * int、str、date、time、datetime、float
     * 
     * array('id' => 'int', 'title' => 'str', 'add_time' => 'datetime')
     * @var array 
     */
    protected $_fillable = null;

    /**
     * 用可填充字段过滤数据
     * @param array $data
     */
    protected function filter($data) {
        if ($this->_fillable && is_array($this->_fillable) && is_array($data)) {

            // 过滤不可填充字段
            $data = array_filter($data, function ($k) {
                return array_key_exists($k, $this->_fillable);
            }, ARRAY_FILTER_USE_KEY);

            // 按字段数据类型处理数据
            array_walk($data, function (&$v, $k) {
                if ($this->_fillable[$k] == 'int') {
                    $v = intval($v);
                }
                if ($this->_fillable[$k] == 'float') {
                    $v = floatval($v);
                }
                if ($this->_fillable[$k] == 'str') {
                    $v = trim($v);
                }
                if ($this->_fillable[$k] == 'date') {
                    $timestamp = strtotime($v);
                    $v = strtotime(date('Y-m-d', $timestamp)) === $timestamp ? $v : null;
                }
                if ($this->_fillable[$k] == 'time') {
                    $timestamp = strtotime(date('Y-m-d') . " {$v}");
                    $v = strtotime(date('Y-m-d H:i:s', $timestamp)) === $timestamp ? $v : null;
                }
                if ($this->_fillable[$k] == 'datetime') {
                    $timestamp = strtotime($v);
                    $v = strtotime(date('Y-m-d H:i:s', $timestamp)) === $timestamp ? $v : null;
                }
            });
        }
        return $data;
    }

    private $_config;
    private $_itemIndexName;
    private $_itemPrefixName;

    public function __construct($server = 'master') {
        $this->_config = Sys::getCfg()['redis'][$server];
        $table = $this->_config['prefix'] . 'table:' . static::$table;
        $this->_itemIndexName = $table . ':index';
        $this->_itemPrefixName = $table . ':item';
    }

    public function add($data) {

        $data = $this->filter($data);
        
        if (!$data) {
            wlog('[' . __LINE__ . ']' . __FILE__ . ':' . 'data is empty', 'redis');
            return false;
        }

        $id = $data[$this->_id];

        $itemName = $this->_itemPrefixName . ':' . $id;

        try {

            $redis = Rd();

            $redis->zAdd($this->_itemIndexName, $id, $itemName);

            $redis->hMset($itemName, $data);

            return true;
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }

    public function delKey($id, $key) {
        $itemName = $this->_itemPrefixName . ':' . $id;

        try {

            $redis = Rd();

            $redis->hDel($itemName, $key);

            return true;
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }

    public function setnx($id, $key, $value) {
        $data[$key] = $value;

        $data = $this->filter($data);

        if (!$data) {
            wlog('[' . __LINE__ . ']' . __FILE__ . ':' . 'data is empty', 'redis');
            return false;
        }

        $itemName = $this->_itemPrefixName . ':' . $id;

        try {

            $redis = Rd();

            $score = $redis->zScore($this->_itemIndexName, $itemName); // $score == $id

            if ($score) {
                return $redis->hSetNx($itemName, $key, $data[$key]);
            }
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }

    public function set($id, $key, $value) {

        $data[$key] = $value;

        $data = $this->filter($data);
        
        if (!$data) {
            wlog('[' . __LINE__ . ']' . __FILE__ . ':' . 'data is empty', 'redis');
            return false;
        }

        return $this->mSet($id, $data);
    }

    public function mSet($id, $data) {

        $data = $this->filter($data);
        
        if (!$data) {
            wlog('[' . __LINE__ . ']' . __FILE__ . ':' . 'data is empty', 'redis');
            return false;
        }

        $itemName = $this->_itemPrefixName . ':' . $id;

        try {

            $redis = Rd();

            $score = $redis->zScore($this->_itemIndexName, $itemName); // $score == $id

            if ($score) {
                $redis->hMset($itemName, $data);
            }

            return true;
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }

    public function del($id) {

        $itemName = $this->_itemPrefixName . ':' . $id;

        try {

            $redis = Rd();

            $redis->zRem($this->_itemIndexName, $itemName);

            $redis->del($itemName);

            return true;
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }

    public function get($id, $field = '') {

        $itemName = $this->_itemPrefixName . ':' . $id;

        try {

            $redis = Rd();

            $score = $redis->zScore($this->_itemIndexName, $itemName); // $score == $id

            if ($score) {

                if ($field) {
                    $item[$field] = $redis->hGet($itemName, $field);
                    return $this->filter($item)[$field];
                }

                $item = $redis->hGetAll($itemName);
                return $this->filter($item);
            }
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }

    public function range($offset = null, $length = null) {
        $arr = array();
        try {

            $redis = Rd();

            $itemNames = $redis->zRange($this->_itemIndexName, $offset, $length);

            foreach ($itemNames as $itemName) {
                $item = $redis->hGetAll($itemName);
                $arr[] = $this->filter($item);
            }
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }
        return $arr;
    }

    public function count() {

        try {

            return Rd()->zCard($this->_itemIndexName);
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }
    
    public function incrBy($id, $field, $int = 1) {

        $itemName = $this->_itemPrefixName . ':' . $id;

        try {

            $redis = Rd();

            $score = $redis->zScore($this->_itemIndexName, $itemName); // $score == $id

            if ($score) {
                $redis->hIncrBy($itemName, $field, $int);
            }

            return true;
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }
    
    public function incrByFloat($id, $field, $float) {

        $itemName = $this->_itemPrefixName . ':' . $id;

        try {

            $redis = Rd();

            $score = $redis->zScore($this->_itemIndexName, $itemName); // $score == $id

            if ($score) {
                $redis->hIncrByFloat($itemName, $field, $float);
            }

            return true;
        } catch (Exception $ex) {
            wlog($ex->getMessage() . PHP_EOL . $ex->getTraceAsString(), 'redis');
        }

        return false;
    }

}
